let THREE=require('three');
import Props from '../general/props';
import Utils from '../general/utils';
import SoftLine from "../graphic/soft-line";

/**
 * 选择器
 @param camera {Camera} 摄像机
 @param rect {object} rect对象{left,top,width,height}
 @param crossEvent {crossEvent} 事件对象：
 @param values {object} 属性对象：
 style:Selector.style.both, {string} 选择样式 pick:点选,box:框选,both:混合
 mode:Selector.mode.multi, {string} 选择模式  single:单选,multi:多选
 enabled:true, {boolean} 是否启用
 autoClear:true, {boolean} 自动清除（框选时影响）
 avoidDoubleClick:true, {boolean} 屏蔽双击
 alwaysClosest:true, {boolean} 总是选择最近（点选时影响）
 doubleClickInterval:200, {number} 双击判定间隔（毫秒）
 */
export default class Selector{
    constructor(camera,rect,crossEvent,values={}){
        console.warn('this class is deprecated now, use new Selector which also support XR!');
        this._crossEvent=crossEvent;
        this._camera=camera;
        this._rect=rect;
        this._values=values;
        this._init();
    }

    /**
     * 选择模式（单选，多选）
     * @type {{single: string, multi: string}}
     */
    static get mode(){
        return {
            single: 'single',
            multi: 'multi'
        };
    }

    /**
     * 选择类型（点选，框选，混合）
     * @type {{pick: string, box: string, both: string}}
     */
    static get style(){
        return {
            both: 'both',
            box: 'box',
            pick: 'pick'
        };
    }

    /**
     * 设置相机
     * @param camera
     */
    set camera(camera){
        this._camera=camera;
    }

    /**
     * 设置相应的视口rect（视口改变时）
     * @param rect
     */
    set rect(rect){
        this._rect=rect;
    }

    /**
     * 获取配置列表
     * @returns {[]}
     */
    get config(){
        return this._config;
    }

    /**
     * 获取选择框材质
     * @returns {ShaderMaterial}
     */
    get selectBoxMaterial(){
        return this._selectBox.material;
    }

    /**
     * 获取事件对象
     * @returns {crossEvent}
     */
    get eventCenter(){
        return this._eventCenter;
    }

    /**
     * 设置选择优先顺序
     * @param classes
     */
    set order(classes){
        this._orders=classes;
    }

    /**
     * 获取选择优先顺序
     * @returns {*|[]}
     */
    get order(){
        return this._orders;
    }

    /**
     * 获取已选取的对象
     * @returns {[]}
     */
    get selected(){
        return this._selected;
    }

    /**
     * 选中时的回调函数
     * @param fuc_obj
     */
    set selectCallback(fuc_obj){
        this._selectCallback=fuc_obj;
    }

    /**
     * 取消选择时的回调函数
     * @param fuc_obj
     */
    set unSelectCallback(fuc_obj){
        this._unselectCallback=fuc_obj;
    }

    /**
     * 选取发生变化时的回调函数
     * @param func
     */
    set changeCallback(func){
        this._changeCallback=func;
    }

    /**
     * 获取可选对象
     * @returns {[]}
     */
    get targets(){
        return this._objects;
    }

    /**
     * 释放未完成的事件
     */
    releaseUnfinishedEvent() {
        this._crossEvent.removeEventListener('leftPointMove', this._onPointerMoveBind);
        this._crossEvent.removeEventListener('leftPointUp', this._onPointerUpBind);
        this._crossEvent.removeEventListener('pointOut', this._onPointerUpBind);
        this._selectStart = false;
        this._isBoxSelect = false;
    }

    /**
     * 添加配置
     * @param config
     */
    addConfig(...config){
        for(let cof of config) {
            let idx = this._config.findIndex((c) => c.type === cof.type);
            if(idx>-1)continue;
            this._config.push(cof);
        }
    }

    /**
     * 移除配置
     * @param config
     */
    removeConfig(...config){
        for(let cof of config) {
            let idx = this._config.findIndex((c) => c.type === cof.type);
            if(idx<0)continue;
            this._config.splice(idx,1);
        }
    }

    /**
     * 清除配置
     */
    clearConfig(){
        this._config=[];
    }

    /**
     * 添加可选对象
     * @param objects
     */
    add(...objects) {
        for (let object of objects) {
            let config=this._getConfig(object);
            if (!config) continue;

            let idx = -1;
            for (let i = 0; i < this._objects.length; i++) {
                if (config.compare(this._objects[i], object)) {
                    idx = i;
                    break;
                }
            }
            if (idx === -1) {
                this._objects.push(object);
            } else {
                console.warn('operate failed,no property config to handle this object!');
            }

        }
    }

    /**
     * 移除可选对象
     * @param objects
     */
    remove(...objects) {
        for (let object of objects) {
            let config = this._getConfig(object);
            if (!config) return;

            let idx = -1;
            for (let i = 0; i < this._objects.length; i++) {
                if (config.compare(this._objects[i], object)) {
                    idx = i;
                    break;
                }
            }
            if (idx !== -1) {
                this._objects.splice(idx, 1);
            }
        }
    }

    /**
     * 清除可选对象
     */
    clear(){
        this._objects=[];
    }

    /**
     * 销毁
     */
    dispose(){
        this._crossEvent.removeEventListener('leftPointDown', this._onPointerDownBind);
        this.clearSelect(true);
        this.clear();
        this._drawDash(2);
        for(let c of this._config){
            c.dispose();
        }
        this._selectBox.dispose();
        for(let br of this._breaker){
            br.dispose();
        }
        this._breaker=null;
        this._config=null;
        this._eventCenter=null;
        this._objects =null;
        this._selected =null;
        this._orders =null;
        this._startPointer =null;
        this._endPointer =null;
        this._selectStart =null;
        this._selectCount =null;
        this._selectStartEvent =null;
        this._selectInEvent =null;
        this._selectEndEvent =null;
        this._selectChangeEvent =null;
        this._selectClearEvent =null;
        this._doubleClickTimer =null;
        this._doubleClickEvent =null;
        this._selectCallback =null;
        this._unselectCallback =null;
        this._changeCallback =null;
        this._tV0=null;
        this._selectBox=null;
        this._isBoxSelect=null;
    }

    /**
     * 选择对象
     * @param object
     * @param fireEvents
     * @param ignoreMode
     */
    select(object, fireEvents=true,ignoreMode=false){
        let oriSelect=[...this._selected];
        let tempSelectObj =object;
        if (this._props.mode.value === Selector.mode.multi||ignoreMode===true) {
            if(!this.ifSelected(tempSelectObj,false)) this._addToSelected(tempSelectObj);
        }
        if (this._props.mode.value === Selector.mode.single&&ignoreMode===false) {
            this.clearSelect(false);
            this._addToSelected(tempSelectObj);
        }
        if(this._selectCallback)this._selectCallback(object);
        if (fireEvents) {
            this._fireEndEvent(this._selected);
            this._fireChangeEvent(oriSelect);
        }
    }

    /**
     * 取消选择对象
     * @param object
     * @param fireEvent
     */
    unSelect(object,fireEvent){
        let index = this.ifSelected(object, true);
        if (index === -1) return;
        let oriSelect=[...this._selected];
        this._selected.splice(index,1);
        if(this._unselectCallback)this._unselectCallback(object);
        if (fireEvent) {
            this._fireEndEvent(this._selected);
            this._fireChangeEvent(oriSelect);
        }
    }

    /**
     * 清除已选择的对象
     * @param fireEvent
     */
    clearSelect(fireEvent=true) {
        this._selected.forEach((o) => {
            if (this._unselectCallback) this._unselectCallback(o);
        });
        let oriLength = this._selected.length;
        let oriSelected = [...this._selected];
        this._selected = [];
        if (oriLength !== 0) this._fireClearEvent();
        if (oriLength !== 0 && fireEvent) {
            this._fireChangeEvent(oriSelected);
            this._fireEndEvent(this._selected);
        }
    }

    /**
     * 对象是否被选择
     * @param object
     * @param returnIndex
     * @returns {boolean|number}
     */
    ifSelected(object,returnIndex) {
        let idx = -1;
        let config=this._getConfig(object);
        for (let i = 0; i < this._selected.length; i++) {
            if(!config.checkType(this._selected[i]))continue;
            if (config.compare(object, this._selected[i])) {
                idx = i;
                break;
            }
        }
        if (returnIndex) return idx;
        return !(idx<0);
    }

    /**
     * 添加中断对象
     * @param breakers
     */
    addBreaker(...breakers){
        for(let br of breakers){
            let has=false;
            for(let b of this._breaker){
                if(b.uuid===br.uuid){
                    has=true;
                    break;
                }
            }
            if(!has)this._breaker.push(br);
        }
    }

    /**
     * 移除中断对象
     * @param breakers
     */
    removeBreaker(...breakers){
        for(let br of breakers){
            for(let i=0;i<this._breaker.length;i++){
                if(this._breaker[i].uuid===br.uuid){
                    this._breaker.splice(i,1);
                    break;
                }
            }
        }
    }

    /**
     * 清除中断对象
     */
    clearBreaker(){
        this._breaker=[];
    }

    //--------------------------------------------------------------------------------------------------------------

    get style(){
        return this._props.style.value;
    }
    set style(style){
        this._props.style.value=style;
    }
    get mode(){
        return this._props.mode.value;
    }
    set mode(mode){
        this._props.mode.value=mode;
    }
    get enabled(){
        return this._props.enabled.value;
    }
    set enabled(bol){
        this._props.enabled.value=bol;
    }
    get autoClear(){
        return this._props.autoClear.value;
    }
    set autoClear(bol){
        this._props.autoClear.value=bol;
    }
    get avoidDoubleClick(){
        return this._props.avoidDoubleClick.value;
    }
    set avoidDoubleClick(bol){
        this._props.avoidDoubleClick.value=bol;
    }
    get alwaysClosest(){
        return this._props.alwaysClosest.value;
    }
    set alwaysClosest(bol){
        this._props.alwaysClosest.value=bol;
    }
    get doubleClickInterval(){
        return this._props.doubleClickInterval.value;
    }
    set doubleClickInterval(num){
        this._props.doubleClickInterval.value=num;
    }
    get pointerThreshold(){
        return this._props.pointerThreshold.value;
    }
    set pointerThreshold(num){
        this._props.pointerThreshold.value=num;
    }

    //-----------------------------------------------------------------------------------------------------------------

    _init(){
        this._initProps();
        this._initVariable();
        this._initSelectBox();
        this._initEventBind();
        this._addEventListener();
        this._initPropCallback();
    }
    _initProps(){
        this._props=new Props({
            style:this._values.style||Selector.style.both,
            mode:this._values.mode||Selector.mode.multi,
            enabled:this._values.enabled!==undefined?this._values.enabled:true,
            autoClear:this._values.autoClear!==undefined?this._values.autoClear:true,
            avoidDoubleClick:this._values.avoidDoubleClick!==undefined?this._values.avoidDoubleClick:true,
            alwaysClosest:this._values.alwaysClosest!==undefined?this._values.alwaysClosest:true,
            doubleClickInterval:this._values.doubleClickInterval||200,
            pointerThreshold:this._values.pointerThreshold||0.5
        });
    }
    _initPropCallback(){
        this._props.onKeyChange('enabled',()=>{
            if(this._isBoxSelect) {
                this._drawDash(2);
                this._isBoxSelect=false;
            }
        });
        this._props.onKeyChange('doubleClickInterval',()=>{
            this._doubleClickTimer.timeInterval=this._props.doubleClickInterval.value;
        });
    }
    _initVariable() {
        this._config=[];
        this._breaker=[];
        this._eventCenter=new THREE.EventDispatcher();
        this._objects = [];
        this._selected = [];
        this._orders = [];
        this._startPointer = new THREE.Vector2();
        this._endPointer = new THREE.Vector2();
        this._selectStart = false;
        this._selectCount = 0;
        this._selectStartEvent = {type: 'selectStart', paras: {pointer: new THREE.Vector2()}};
        this._selectInEvent = {type: 'selectIn', paras: {pointer: new THREE.Vector2()}};
        this._selectEndEvent = {type: 'selectEnd', paras: {value: {}, pointer: new THREE.Vector2()}};
        this._selectChangeEvent = {type: 'change', paras: {previous:[],current:[]}};
        this._selectClearEvent = {type: 'selectClear'};
        this._doubleClickTimer = {timer: {}, timeInterval: this._props.doubleClickInterval.value, count: 0};
        this._doubleClickEvent = {type: 'doubleClick', paras: {object: undefined}};
        this._selectCallback = null;
        this._unselectCallback = null;
        this._changeCallback = null;
        this._tV0=new THREE.Vector3();
        this._selectBox=null;
        this._isBoxSelect=false;
    }
    _getConfig(object){
        for (let cof of this._config) {
            if (cof.checkType(object)) {
                return cof;
            }
        }
        return null;
    }
    _initSelectBox(){
        this._selectBox = new SoftLine([
            new THREE.Vector3(1,1,0),
            new THREE.Vector3(-1,0,0),
            new THREE.Vector3(-1,-1,0),
            new THREE.Vector3(1,-1,0),
            new THREE.Vector3(1,1,0)
        ], {
            width:0.02,
            dashFill:false,
            dashSize:0.005,
            gapSize:0.005,
            sizeAttenuation:true,
        });
        this._selectBox.material.depthFunc=THREE.AlwaysDepth;
    }
    _initEventBind(){
        this._onPointerDownBind=(e)=>{
            this._onPointerDown(e);
        };
        this._onPointerMoveBind=(e)=>{
            this._onPointerMove(e);
        };
        this._onPointerUpBind=(e)=>{
            this._onPointerUp(e);
        };
        this._fireStartEvent=(pointer=new THREE.Vector2())=>{
            this._selectStartEvent.paras.pointer.copy(pointer);
            this._eventCenter.dispatchEvent(this._selectStartEvent);
        };
        this._fireSelectInEvent=(pointer=new THREE.Vector2())=>{
            this._selectInEvent.paras.pointer.copy(pointer);
            this._eventCenter.dispatchEvent(this._selectInEvent);
        };
        this._fireEndEvent=(selected,pointer)=>{
            this._selectEndEvent.paras.value=selected;
            this._selectEndEvent.paras.pointer.copy(pointer?pointer:this._endPointer);
            this._eventCenter.dispatchEvent(this._selectEndEvent);
        };
        this._fireChangeEvent=(oriSelected=[])=>{
            let isChange=(()=>{
                if(oriSelected.length!==this._selected.length)return true;
                for(let j=0;j<oriSelected.length;j++) {
                    let cofO=this._getConfig(oriSelected[j]);
                    let has=false;
                    for (let i = 0; i < this._selected.length; i++) {
                        if(!cofO.checkType(this._selected[i]))continue;
                        if (cofO.compare(oriSelected[j], this._selected[i])){
                            has=true;
                            break;
                        }
                    }
                    if(!has)return true;
                }
                return false;
            })();
            if(!isChange)return;
            this._selectChangeEvent.paras.previous=oriSelected;
            this._selectChangeEvent.paras.current=this._selected;
            if(this._changeCallback)this._changeCallback();
            this._eventCenter.dispatchEvent(this._selectChangeEvent);
        };
        this._fireDoubleClickEvent=(object)=>{
            this._doubleClickEvent.paras.object=object;
            this._eventCenter.dispatchEvent(this._doubleClickEvent);
        };
        this._fireClearEvent=()=>{
            this._eventCenter.dispatchEvent(this._selectClearEvent);
        };
    }
    _addEventListener(){
        this._crossEvent.addEventListener('leftPointDown', this._onPointerDownBind);
    }
    _onPointerDown(event){
        if (this._selectStart === true || this._props.enabled.value === false)return;
        let pointer = event.isTouch ? event.changedTouches[0] : event;
        this._startPointer.set(pointer.clientX,pointer.clientY);
        this._fireStartEvent(this._startPointer);
        this._selectStart = true;
        this._crossEvent.addEventListener('leftPointMove', this._onPointerMoveBind);
        this._crossEvent.addEventListener('leftPointUp', this._onPointerUpBind);
        this._crossEvent.addEventListener('pointOut', this._onPointerUpBind);
    }
    _onPointerMove(event){
        if (this._selectStart === false || this._props.enabled.value === false)return;
        for(let b of this._breaker){
            b.interrupt=true;
        }
        let pointer = event.isTouch ? event.changedTouches[0] : event;
        this._endPointer.set(pointer.clientX,pointer.clientY);
        let bool=Utils.equal(this._startPointer, this._endPointer, 'vector2', 0.5);
        if (this._props.style.value === Selector.style.pick) {
            if (!bool) {
                this._crossEvent.removeEventListener('leftPointMove', this._onPointerMoveBind);
                this._crossEvent.removeEventListener('leftPointUp', this._onPointerUpBind);
                this._crossEvent.removeEventListener('pointOut', this._onPointerUpBind);
                this._selectStart = false;
            }
            return;
        }

        if (this._isBoxSelect === false&&!bool) {
            this._isBoxSelect=true;
            this._drawDash(0,this._startPointer,this._endPointer);
        } else if(this._isBoxSelect){
            this._drawDash(1,this._startPointer,this._endPointer);
        }
        this._fireSelectInEvent(this._endPointer);
    }
    _onPointerUp(event){
        if (this._props.enabled.value === false || this._selectStart === false)return;
        for(let b of this._breaker){
            b.interrupt=false;
        }
        let pointer = event.isTouch ? event.changedTouches[0] : event;
        this._endPointer.set(pointer.clientX,pointer.clientY);
        let oriSelect=[...this._selected];
        if (this._isBoxSelect) {
            this._isBoxSelect=false;
            this._boxSelect();
            this._fireEndEvent(this._selected,this._endPointer);
            this._fireChangeEvent(oriSelect);
        } else {
            if(Utils.equal(this._startPointer,this._endPointer,'vector2',this._props.pointerThreshold.value)) {
                if(this._props.avoidDoubleClick.value){
                    this._doubleClickTimer.count++;
                    Utils.waitToExecuteTime(this._doubleClickTimer.timer,this._doubleClickTimer.timeInterval,()=>{
                        if(this._doubleClickTimer.count>1){
                            this._fireDoubleClickEvent(this._getPickObject());
                        }else {
                            this._pickSelect();
                            this._fireEndEvent(this._selected,this._endPointer);
                            this._fireChangeEvent(oriSelect);
                        }
                        this._doubleClickTimer.count=0;
                    },false);
                }else {
                    this._pickSelect();
                    this._fireEndEvent(this._selected,this._endPointer);
                    this._fireChangeEvent(oriSelect);
                }
            }
        }
        this._crossEvent.removeEventListener('leftPointMove', this._onPointerMoveBind);
        this._crossEvent.removeEventListener('leftPointUp', this._onPointerUpBind);
        this._crossEvent.removeEventListener('pointOut', this._onPointerUpBind);
        this._selectStart = false;
    }
    _boxSelect(){
        this._drawDash(2);
        let count = 0;
        let objects=this._objects;
        for(let object of objects){
            let config=this._getConfig(object);
            if(!config)continue;
            if(config.intersectRect(object,this._startPointer,this._endPointer,this._camera,this._rect)) {
                if (this.ifSelected(object, false)) {
                    this.unSelect(object, false);
                    count++;
                } else {
                    this.select(object, false);
                    count++;
                    if (this._props.mode.value === Selector.mode.single) break;
                }
            }
        }
        if(this._props.autoClear.value){
            if(count===0)this.clearSelect(false);
        }
    }
    _pickSelect(){
        if (this._props.style.value !== Selector.style.box) {
            let intersect = this._intersectObjects(this._endPointer,this._objects);
            if (intersect) {
                if (this.ifSelected(intersect.object, false)) {
                    this.unSelect(intersect.object, false);
                } else {
                    this.select(intersect.object, false);
                }
            } else {
                this.clearSelect(false);
            }
        }
    }
    _addToSelected(obj){
        this._selected.push(obj);
    }
    _getPickObject(){
        let obj=null;
        let intersect = this._intersectObjects(this._endPointer,this._objects);
        if (intersect) {
            obj=intersect.object;
        }
        return obj;
    }
    _intersectObjects(pointer,objects){
        let intersections=[];
        for(let object of objects){
            let info=[];
            let config=this._getConfig(object);
            if(!config)continue;
            config.intersectPointer(object,pointer,info,this._camera,this._rect);
            intersections.push(...info);
        }
        if(intersections.length===0)return null;
        if(!this._props.alwaysClosest.value) {
            if (this._selectCount > intersections.length - 1) this._selectCount = 0;
            let result = intersections[this._selectCount] ? intersections[this._selectCount] : false;
            this._selectCount++;
            return result;
        }else{
            let indices=(()=>{
                let tp=[];
                for(let i=0;i<this._orders.length;i++){
                    tp.push(null);
                }
                return tp;
            })();
            for(let i=0;i<intersections.length;i++){
                let temp=intersections[i].object;
                for(let j=0;j<indices.length;j++){
                    if(indices[j]===null&&(temp instanceof this._orders[j])){
                        indices[j]=i;
                        if(j===0)return intersections[i];
                    }
                }
            }
            for(let i=0;i<indices.length;i++){
                if(indices[i]!==null)return intersections[indices[i]];
            }
            return intersections[0];
        }
    }
    _drawDash(state,startPointer,endPointer){
        if(state===2) {
            let scene = Utils.getParentScene(this._selectBox);
            if (scene) scene.remove(this._selectBox);
            return;
        }
        if(this._objects.length===0)return;
        let scene=Utils.getParentScene(this._objects[0]);
        if(!scene)return;
        let s=startPointer,e=endPointer;
        this._camera.getWorldDirection(this._tV0);
        this._tV0.normalize().add(this._camera.position);
        let fst=Utils.screenPointToWorld(this._rect,this._camera,this._tV0,s,false);
        let sec=Utils.screenPointToWorld(this._rect,this._camera,this._tV0,new THREE.Vector2(s.x,e.y),false);
        let thd=Utils.screenPointToWorld(this._rect,this._camera,this._tV0,e,false);
        let fou=Utils.screenPointToWorld(this._rect,this._camera,this._tV0,new THREE.Vector2(e.x,s.y),false);
        if(state===1) {
            this._selectBox.vertices=[fst, sec, thd, fou, fst];
        }else if(state===0){
            this._selectBox.vertices=[fst, sec, thd, fou, fst];
            scene.add(this._selectBox);
        }
    }
}

